package com.ruoyi.nettywebsockt;

import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.handler.websocket.MyWebSocketChannelInboundHandler;
import com.ruoyi.handler.websocket.MyWebSocketServerProtocolHandler;
import com.ruoyi.handler.websocket.NettyWebSocketParamHandler;
import com.ruoyi.manager.WebSocketChannelManager;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

/**
 * 利用netty创建的websocket服务端。
 */
@Slf4j
@Component
public class WebSocketServer {

    @Value("${websocket.port}")
    private int port;

    @Value("${websocket.websocketPath}")
    private String websocketPath;

    // 服务端启动辅助类，用于设置TCP相关参数
    private ServerBootstrap bootstrap;
    // 获取Reactor线程池
    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;

//    @PostConstruct
    public void startWebSocketServer() {
        // 服务端启动辅助类，用于设置TCP相关参数
        bootstrap = new ServerBootstrap();
        // 获取Reactor线程池
        bossGroup = new NioEventLoopGroup();
        workerGroup = new NioEventLoopGroup();

        NettyWebSocketParamHandler nettyWebSocketParamHandler = new NettyWebSocketParamHandler();
//        MyWebSocketChannelInboundHandler myWebSocketChannelInboundHandler = new MyWebSocketChannelInboundHandler();

        // 设置为主从线程模型
        bootstrap.group(bossGroup, workerGroup)
                // 设置服务端NIO通信类型
                .channel(NioServerSocketChannel.class)
                // 设置ChannelPipeline，也就是业务职责链，由处理的Handler串联而成，由从线程池处理
                .childHandler(new ChannelInitializer<Channel>() {
                    // 添加处理的Handler，通常包括消息编解码、业务处理，也可以是日志、权限、过滤等
                    @Override
                    protected void initChannel(Channel ch) throws Exception {
                        // 获取职责链
                        ChannelPipeline pipeline = ch.pipeline();

                        //基于http协议，因此添加http编解码器
                        pipeline.addLast("http-codec", new HttpServerCodec());
                        //http消息聚合器,将http协议下分段传输的数据聚合到一起，512*1024为接收的最大content-length
                        pipeline.addLast("aggregator", new HttpObjectAggregator(8192));
                        //支持异步发送大的码流(大的文件传输)，尤其适用于大文件写入，方便管理状态，不占用过多的内存，防止java内存溢出。
                        //一个{@link ChannelHandler}，它增加了对写入大型数据流的支持既不花费大量内存，也会不导致{@link OutOfMemoryError}。
                        // 大型数据流（例如文件传输）需要在{@link ChannelHandler}实现中进行复杂的状态管理。使用{@link ChunkedWriteHandler}管理如此复杂的状态以便您可以毫无困难地发送大量数据流。
                        pipeline.addLast("http-chunked", new ChunkedWriteHandler());
//                        pipeline.addLast(new StringDecoder());
//                        pipeline.addLast(new StringEncoder());
                        //设置一个空闲状态处理程序（心跳机制），读空闲，写空闲，读写空闲
                        pipeline.addLast(new IdleStateHandler(60000, 0, 0));
                        // 自定义参数处理器，必须放在WebSocketServerProtocolHandler处理器之前
                        pipeline.addLast(nettyWebSocketParamHandler);
                        //将服务器协议升级为WebSocket协议保持长连接 处理握手及帧的传递
                        //升级协议是通过修改状态码实现的 200升级为101
                        //WebSocket 长连接消息传递是通过帧的形式进行传递的
                        //帧 继承抽象类 WebSocketFrame 有六个子类 帧的处理由管道中下一个handler进行处理
                        //WebSocket请求形式 ：ws://localhost:1234/hello
//                        pipeline.addLast(new WebSocketServerProtocolHandler(websocketPath));
                        pipeline.addLast(new MyWebSocketServerProtocolHandler(websocketPath));
//                        pipeline.addLast("handler", new WebSocketFrameHandler());
//                        pipeline.addLast(myWebSocketChannelInboundHandler);
                        pipeline.addLast(new MyWebSocketChannelInboundHandler());
                    }
                })
                // bootstrap 还可以设置TCP参数，根据需要可以分别设置主线程池和从线程池参数，来优化服务端性能。
                // 其中主线程池使用option方法来设置，从线程池使用childOption方法设置。
                // backlog表示主线程池中在套接口排队的最大数量，队列由未连接队列（三次握手未完成的）和已连接队列组成
                .option(ChannelOption.SO_BACKLOG, 1024)
                // 表示连接保活，相当于心跳机制，默认为7200s
                .childOption(ChannelOption.SO_KEEPALIVE, true);

        bootstrap.bind(port).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future1) throws Exception {
                if (future1.isSuccess()) {
                    log.info("WebSocketServer启动成功, port: {}, websocketPath: {}", port, websocketPath);
                    Channel channel = future1.channel();
                    channel.closeFuture().addListener((ChannelFutureListener) future2 -> {
                        bossGroup.shutdownGracefully();
                        workerGroup.shutdownGracefully();
                    });
                } else {
                    log.error("WebSocketServer启动失败, cause: {}", future1.cause().getMessage());
                }
            }
        });

    }

    /**
     * Springboot关闭时关闭WebSocketServer
     */
    @PreDestroy
    private void destroy() throws InterruptedException {
        log.info("NettyBooster.destroy --> : 关闭 WebSocketServer...");
        if (ObjectUtil.isNotNull(bossGroup)) {
            bossGroup.shutdownGracefully().sync();
        }
        if (ObjectUtil.isNotNull(workerGroup)) {
            workerGroup.shutdownGracefully().sync();
        }
        log.info("WebSocketServer.destroy --> : WebSocketServer 已关闭!");
        WebSocketChannelManager.removeAllChannel();
    }

}
